package user;

import java.io.Serializable;

import net.ClientCommunicator;
import messages.Message;
import game.Player;
import core.Controller;
import core.ControllerObserver;
import event.*;
import exceptions.CommunicationException;


/**
 * User
 * 
 * User is the super class of every kind of user allowed by the system.
 * There are subclasses for different roles of users. For example,
 * instances of this class represent normal users while instances of the
 * Admin class (which extends this one) represent administrators.
 * 
 * Communication with the client application is delegated to a 
 * ClientCommunicator.
 * 
 * This class implements the ControllerObserver interface meaning it will
 * be notified about every major event ocurring inside the server. How it
 * handles the event (ignoring or forwarding it to the client application) 
 * depends on the specific message properties and on the user role. 
 * For instance, normal users will ignore messages regarding authentication
 * errors from other clients, while an administrator may want to see
 * them all. By overriding the methods implemented by the ControllerObserver,
 * one can model different behaviours for different user roles.
 * 
 */
public class User implements ControllerObserver, Serializable {
	
	public static boolean isValidRole(String role) {
		try {
			Class.forName("user." + role);
			return true;
		} catch (ClassNotFoundException e) {
			return false;
		}
	}
	
	
	public static Class getRoleClass(String role) {
		try {
			return Class.forName("user." + role);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		
		return null;
	}
	
	
	/**
	 * Constructor.
	 * 
	 * Subscribes to the Controller events.
	 */
	public User(ClientCommunicator communicator, Player player) {
		this.communicator = communicator;
		this.player = player;
		
		this.controller = Controller.getInstance();
		this.controller.subscribeEvents(this);
	}
	
	
	/**
	 * Receives a message from the client application.
	 * 
	 * @return The message read.
	 * @throws CommunicationException
	 */
	public Message receive() {
		return this.getCommunicator().receive();

	}
	
	
	/**
	 * Sends a message to the client application.
	 * 
	 * @param message The message to be sent.
	 */
	public void send(Message message) {
		controller.getMessageManager().send(this, message);
	}
	
	
	/**
	 * Handles the WALL event.
	 */
	public void wallEventHandler(WallEventArgs args) { 
		this.send(args.getOriginalMessage());
	}
	
	
	/**
	 * Handles the Chat event.
	 */
	public void chatEventHandler(ChatEventArgs args) { 
		if (this.getLogin().equals(args.getOriginalMessage().getDestinyLogin()))
			this.send(args.getOriginalMessage());
	}
	
	
	/**
	 * Handles the ChatFailed event.
	 * 
	 * Sends the ChatFailed message to the player who originated the invalid chat.
	 */
	public void chatFailedEventHandler(ChatFailedEventArgs args) { 
		if (this.getLogin().equals(args.getMessage().getOriginalMessage().getDestinyLogin()))
			this.send(args.getMessage());
	}
	
	
	/**
	 * Handles the UserLoggedIn event.
	 * Sends the message in args.getMessage() to the client.
	 */
	public void userLoggedInEventHandler(UserLoggedInEventArgs args) { 
		if (!args.getMessage().isPrivateMessage() || args.getMessage().getPlayer().equals(this.player))
			this.send(args.getMessage());
	}
	
	
	/**
	 * Handles the UserLoggedOut event.
	 * Sends the LogOut message to every client.
	 * 
	 * If this is the user instance that logged out, we must remove it
	 * from the controller listeners list.
	 */
	public void userLoggedOutEventHandler(UserLoggedOutEventArgs args) {
		this.send(args.getMessage());
		
		if (this.getLogin().equals(args.getMessage().getPlayerLogin())) {
			
			controller.unsubscribeEvents(this);
			//this.communicator.close();
		}
	}
	
	
	/**
	 * Handles the RegistrationSuccessful event.
	 * Sends the message in args.getMessage() to the new client.
	 * 
	 * Other users ignore this event.
	 */
	public void registrationSuccessfulEventHandler(RegistrationSuccessfulEventArgs args) { 
		if (args.getMessage().getLogin().equals(this.getLogin()))
			this.send(args.getMessage());
	}
	
	
	/**
	 * Handles the RegistrationFailed event.
	 * Does nothing: the user who tried to register was already notified.
	 * 
	 * Other users ignore this event.
	 */
	public void registrationFailedEventHandler(RegistrationFailedEventArgs args) { 
		
	}
	
	
	/**
	 * Handles the InvitationReceived event.
	 * Sends the invitation message in args.getMessage() to the destiny and to the sender.
	 * Other users ignore this event.
	 */
	public void invitationReceivedEventHandler(InvitationReceivedEventArgs args) {
		if (args.getOriginalMessage().getDestinyLogin().equals(this.getLogin()) ||
			args.getOriginalMessage().getOriginLogin().equals(this.getLogin()))
			this.send(args.getOriginalMessage());
	}
	
	
	/**
	 * Handles the InvalidInvitationReceived event.
	 * Sends the invalid invitation message in args.getMessage() to the user who
	 * sent the invitation.
	 * 
	 * Other users ignore this event.
	 */
	public void invalidInvitationReceivedEventHandler(InvalidInvitationReceivedEventArgs args) {
		if (args.getMessage().getOriginalMessage().getOriginLogin().equals(this.getLogin()))
			this.send(args.getMessage());
	}
	
	
	/**
	 * Handles the InvitationTimedOut event.
	 * Sends the message in args.getMessage() to every player involved
	 * in the invitation.
	 * 
	 * Other users ignore this event.
	 */
	public void invitationTimedOutEventHandler(InvitationTimedOutEventArgs args) {
		if (args.getInvitation().getSender().getLogin().equals(this.getLogin()) ||
			args.getInvitation().getDestiny().getLogin().equals(this.getLogin())) {
			this.send(args.getMessage());		
		}
	}
	
	
	/**
	 * Handles the InvitationReply event.
	 * Sends the reply message in args.getMessage() to every player involved
	 * in the invitation.
	 * 
	 * Other users ignore this event.
	 */
	public void invitationReplyEventHandler(InvitationReplyEventArgs args) {
		if (args.getInvitation().getSender().getLogin().equals(this.getLogin()) ||
			args.getInvitation().getDestiny().getLogin().equals(this.getLogin())) {
			this.send(args.getOriginalMessage());		
		}
	}
	
	
	/**
	 * Handles the InvalidInvitationReply event.
	 * Sends the InvalidInvitationReply message in args.getMessage() to the client
	 * who sent the invalid reply.
	 * 
	 * Other users ignore this event.
	 */
	public void invalidInvitationReplyEventHandler(InvalidInvitationReplyEventArgs args) {
		if (args.getSender().getLogin().equals(this.getLogin()))
			this.send(args.getMessage());		
	}
	
	
	/**
	 * Handles the MatchStarted event.
	 * Informs both players that a new match will start. 
	 * 
	 * Other users ignore this event.
	 */
	public void matchStartedEventHandler(MatchStartedEventArgs args) {
		if (args.getMessage().getPlayers().contains(this.getPlayer())) {
			this.send(args.getMessage());
		}
	}
	
	
	/**
	 * Handles the PlayerMove event.
	 * Informs every player in the match about the move performed. 
	 * 
	 * Other users ignore this event.
	 */
	public void playerMoveEventHandler(PlayerMoveEventArgs args) {
		if (args.getPlayers().contains(this.getPlayer())) {
			this.send(args.getMessage());
		}
	}
	
	
	/**
	 * Handles the InvalidMove event.
	 * Informs the player that his move is invalid.
	 * 
	 * Other users ignore this event.
	 */
	public void invalidMoveEventHandler(InvalidMoveEventArgs args) {
		if (args.getMessage().getPlayerLogin().equals(this.getPlayer().getLogin())) {
			this.send(args.getMessage());
		}
	}
	
	
	/**
	 * Handles the ListPlayers event.
	 * Sends the list of players to the user who requested it.
	 * 
	 * Other users ignore this event.
	 */
	public void listPlayersEventHandler(ListPlayersEventArgs args) {
		if (args.getPlayerLogin().equals(this.getPlayer().getLogin())) {
			this.send(args.getMessage());
		}
	}
	
	
	/**
	 * Handles the MatchEnded event.
	 * Informs everyone about the end of a match. 
	 * 
	 * Other users ignore this event.
	 */
	public void matchEndedEventHandler(MatchEndedEventArgs args) {
		this.send(args.getMessage());
	}
	
	
	/**
	 * Handles the LoginFailed event.
	 */
	public void loginFailedEventHandler(LoginFailedEventArgs args) { }
	
	
	/**
	 * Handles the ClientPing event.
	 * Sends the ping to every user.
	 */
	public void clientPingEventHandler(ClientPingEventArgs args) { 
		this.send(args.getMessage());
	}

	
	
	public void pingEventHandler(PingEventArgs args) { }
	
	
	/**
	 * Handles the ListMatchsReply event.
	 * Sends the list of matchs to the user who requested it.
	 * 
	 * Other users ignore this event.
	 */
	public void listMatchsReplyEventHandler(ListMatchsReplyEventArgs args) {
		if (args.getLogin().equals(this.getPlayer().getLogin())) {
			this.send(args.getMessage());
		}
	}
	
	
	/**
	 * Handles the ListGamesReply event.
	 * Sends the list of games to the user who requested it.
	 * 
	 * Other users ignore this event.
	 */
	public void listGamesReplyEventHandler(ListGamesReplyEventArgs args) {
		if (args.getLogin().equals(this.getPlayer().getLogin())) {
			this.send(args.getMessage());
		}
	}
	
	
	/**
	 * Handles the UserDetailsReply event.
	 * Sends the details to the user who requested it.
	 * 
	 * Other users ignore this event.
	 */
	public void userDetailsReplyEventHandler(UserDetailsReplyEventArgs args) {
		if (args.getPlayerLogin().equals(this.getPlayer().getLogin())) {
			this.send(args.getMessage());
		}
	}
	
	
	/**
	 * Handles the InvalidUserDetailsRequest event.
	 * Sends the error message to the user who made the request.
	 * 
	 * Other users ignore this event.
	 */
	public void invalidUserDetailsRequestEventHandler(InvalidUserDetailsRequestEventArgs args) {
		if (args.getLogin().equals(this.getPlayer().getLogin())) {
			this.send(args.getMessage());
		}		
	}

	
	/**
	 * Handles the AccountCreationFailed event.
	 * Do nothing: users can't create accounts.
	 */
	public void accountCreationFailedEventHandler(AccountCreationFailedEventArgs args) { }


	/**
	 * Handles the AccountCreationSuccessful event.
	 * Do nothing: users can't create accounts.
	 */
	public void accountCreationSuccessfulEventHandler(AccountCreationSuccessfulEventArgs args) { }


	/**
	 * Handles the AccountEditionFailed event.
	 * Sends the error message to the user who made the request.
	 * 
	 * Other users ignore this event.
	 */
	public void accountEditionFailedEventHandler(AccountEditionFailedEventArgs args) {
		if (args.getLogin().equals(this.getPlayer().getLogin())) {
			this.send(args.getMessage());
		}
	}


	/**
	 * Handles the AccountEditionSuccessful event.
	 * Sends a confirmation message to the user who made the request.
	 * 
	 * Other users ignore this event.
	 */
	public void accountEditionSuccessfulEventHandler(AccountEditionSuccessfulEventArgs args) {
		if (args.getLogin().equals(this.getPlayer().getLogin())) {
			this.send(args.getMessage());
		}
	}


	/**
	 * Handles the AccountRemotionFailed event.
	 * Do nothing: users can't remove accounts.
	 */
	public void accountRemotionFailedEventHandler(AccountRemotionFailedEventArgs args) { }


	/**
	 * Handles the AccountRemotionSuccessful event.
	 * Do nothing: users can't remove accounts.
	 */
	public void accountRemotionSuccessfulEventHandler(AccountRemotionSuccessfulEventArgs args) { }
	
	
	/**
	 * Handles the ListPluginsReply event.
	 * Do nothing: users can't list the plugins.
	 */
	public void listPluginsReplyEventHandler(ListPluginsReplyEventArgs args) {	}


	/**
	 * Handles the PluginOperationFailed event.
	 * Do nothing: users can't mess around with plugins.
	 */
	public void pluginOperationFailedEventHandler(PluginOperationFailedEventArgs args) { }


	/**
	 * Handles the PluginOperationSuccessful event.
	 * Do nothing: users can't mess around with plugins.
	 */
	public void pluginOperationSuccessfulEventHandler(PluginOperationSuccessfulEventArgs args) { }
	
	
	/**
	 * Returns the login of this user.
	 * 
	 * @return The login of this user.
	 */ 
	public String getLogin() {
		return this.player.getLogin();
	}
	
	
	/**
	 * Returns the Player associated with this client.
	 * @return The Player associated with this client.
	 */
	public Player getPlayer() {
		return this.player;
	}


	public ClientCommunicator getCommunicator() {
		return communicator;
	}
	
	
	/**
	 * Used to set the new ClientCommunicator when we are rebuilding a server-to-client socket connection.
	 * 
	 * @param newClientCommunicator The new ClientCommunicator.
	 */
	public void setCommunicator(ClientCommunicator newClientCommunicator) {
		this.communicator = newClientCommunicator;
	}
	
	
	public synchronized int incLastMessageSentId() {
		return lastMessageSentId++;
	}
	
	
	public synchronized int getLastMessageReceivedId() {
		return lastMessageReceivedId;
	}
	
	
	public synchronized void setLastMessageReceivedId(int lastMessageReceivedId) {
		this.lastMessageReceivedId = lastMessageReceivedId;
	}
	
	
	@Override
	public boolean equals(Object o) {
		if (o instanceof User)
			return this.getPlayer().equals(((User) o).getPlayer());
		
		return false;
	}
	
	
	private Controller controller;
	private ClientCommunicator communicator; 	// The communicator interface used to communicate with the client.
	private Player player; 						// The player associated with this client
	private int lastMessageSentId = 0;			// The ID of the last message sent to this user.
	private int lastMessageReceivedId = 0;		// The ID of the last message received from this user.
	
	private static final long serialVersionUID = 1L;
}
